// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
/*DeviceHandler的用处：
 * 连接设备，连接后的通信。
*/

#ifndef DEVICEHANDLER_H
#define DEVICEHANDLER_H

#include "bluetoothbaseclass.h"

#include <QLowEnergyController>
#include <QLowEnergyService>
#include <QTemporaryFile>

#include <qqml.h>

#include <QDateTime>
#include <QList>
#include <QTimer>
//#include <QFile>
#include "clientthread.h"

/*MCU侧有问题，会响应2个CA。（CA=0x18）
*/
#define STA_IDLE            0           //初始状态
#define STA_CCC             1           //等待3个连续的'C'
#define STA_0_ACK           2           //等待第0包的ACK。
#define STA_1_C             3           //等待索要第1包的'C'。
#define STA_N_ACK           4           //等待已发送数据的ACK。N是指每1KiB发送完成，都等待ACK。文件发送过程中，需要等个N个ACK。
#define STA_EOT_SEND        7           //已发送EOT，等待发送完成。
#define STA_EOT_ACK         5           //已发送EOT，等待ACK。
#define STA_000_ACK         6           //已发送全0包，等待应答

#define STA_KEY             10          //发送按键状态
#define STA_03H             11          //modbus 03h的响应状态
#define STA_06H             12          //modbus 06h的响应状态
#define STA_10H             13          //modbus 10h的响应状态
#define STA_CURVE           20          //曲线状态，一直在收数据。

#define ACK                 (0x06)
#define NAK                 (0x15)
#define CA                  (0x18)

/*更多地址
*/
#define addr_cal_0                  2
#define addr_cal_t                  addr_cal_0
#define addr_cal_1                  4
#define addr_cal_2                  6
#define addr_cal_3                  8
#define addr_cal_4                  10

#define addr_laji_curve             50      //和FLASH参数无关。


#define addr_com_plc_addr           100
#define addr_com_plc_baud_idx       102
#define addr_com_plc_mode_idx       104
#define addr_com_plc_format_idx     106
#define addr_com_interval_idx       108
#define addr_com_plc_protocol_idx   110     //已作废，因为和addr_com_plc_mode_idx是一个东西。

#define addr_laji_gap_min_idx       130
#define addr_laji_gap_max_idx       131
#define addr_laji_wt_hold_tmo_idx   132
#define addr_laji_gps_gap           133
#define addr_laji_zzone_val         134
#define addr_laji_zzone_tmo         135
#define addr_laji_dtu_ip_port       136     //需要特殊处理

#define addr_zero_auto_sw_idx       200
#define addr_range_zero_trace_idx	202
#define addr_range_stab_idx			204
#define addr_range_zero             206
#define addr_filter_dig_idx			208
#define addr_filter_stab_idx        210
#define addr_sps_idx                212
#define addr_in1_define_idx			213
#define addr_out1_define_idx        214
#define addr_out2_define_idx        215

#define addr_cal_pt_idx             220
#define addr_cal_fd_idx             221
#define addr_cal_cp                 222
#define addr_cal_pwd                223     //


class DeviceInfo;

class DeviceHandler : public BluetoothBaseClass
{
    Q_OBJECT

    Q_PROPERTY(bool measuring READ measuring NOTIFY measuringChanged)           //使用Q_PROPERTY()，以在QML中使用。
    Q_PROPERTY(bool alive READ alive NOTIFY aliveChanged)
    Q_PROPERTY(int hr READ hr NOTIFY statsChanged)                              //实时heartrate
    Q_PROPERTY(int maxHR READ maxHR NOTIFY statsChanged)                        //heartrate最大值
    Q_PROPERTY(int minHR READ minHR NOTIFY statsChanged)
    Q_PROPERTY(float average READ average NOTIFY statsChanged)
    Q_PROPERTY(int time READ time NOTIFY statsChanged)
    Q_PROPERTY(float calories READ calories NOTIFY statsChanged)
    Q_PROPERTY(AddressType addressType READ addressType WRITE setAddressType)
    //Q_PROPERTY(QByteArray wrValue READ wrValue CONSTANT )
    Q_PROPERTY(float ratio      READ ratio      WRITE setRatio  NOTIFY ratioChanged)            //在属性系统中的名字是：ratio。
    Q_PROPERTY(bool update_ok   READ update     WRITE setUpdate NOTIFY update_okChanged)        //READ函数可以和属性名称不一样。

    //ParaPlc.qml涉及的控件绑定属性数据
    Q_PROPERTY(int plc_addr             READ plc_addr               WRITE setPlc_addr               NOTIFY paraplcChanged)
    Q_PROPERTY(int plc_baud_idx         READ plc_baud_idx           WRITE setPlc_baud_idx           NOTIFY paraplcChanged)
    Q_PROPERTY(int plc_fmt_idx          READ plc_fmt_idx            WRITE setPlc_fmt_idx            NOTIFY paraplcChanged)
    Q_PROPERTY(int plc_interval_idx     READ plc_interval_idx       WRITE setPlc_interval_idx       NOTIFY paraplcChanged)
    Q_PROPERTY(int plc_protocol_idx     READ plc_protocol_idx       WRITE setPlc_protocol_idx       NOTIFY paraplcChanged)

    Q_PROPERTY(int zero_auto_sw_idx     READ zero_auto_sw_idx       WRITE setZero_auto_sw_idx       NOTIFY parasetChanged)
    Q_PROPERTY(int range_zero_trace_idx READ range_zero_trace_idx   WRITE setRange_zero_trace_idx   NOTIFY parasetChanged)
    Q_PROPERTY(int range_stab_idx       READ range_stab_idx         WRITE setRange_stab_idx         NOTIFY parasetChanged)
    Q_PROPERTY(int range_zero           READ range_zero             WRITE setRange_zero             NOTIFY parasetChanged)
    Q_PROPERTY(int filter_dig_idx       READ filter_dig_idx         WRITE setFilter_dig_idx         NOTIFY parasetChanged)
    Q_PROPERTY(int filter_stab_idx      READ filter_stab_idx        WRITE setFilter_stab_idx        NOTIFY parasetChanged)
    Q_PROPERTY(int sps_idx              READ sps_idx                WRITE setSps_idx                NOTIFY parasetChanged)
    Q_PROPERTY(int in1_define_idx       READ in1_define_idx         WRITE setIn1_define_idx         NOTIFY parasetChanged)
    Q_PROPERTY(int out1_define_idx      READ out1_define_idx        WRITE setOut1_define_idx        NOTIFY parasetChanged)
    Q_PROPERTY(int out2_define_idx      READ out2_define_idx        WRITE setOut1_define_idx        NOTIFY parasetChanged)

    Q_PROPERTY(int laji_gap_min_idx     READ laji_gap_min_idx       WRITE setLaji_gap_min_idx       NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_gap_max_idx     READ laji_gap_max_idx       WRITE setLaji_gap_max_idx       NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_hold_tmo_idx    READ laji_hold_tmo_idx      WRITE setLaji_hold_tmo_idx      NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_gps_gap         READ laji_gps_gap           WRITE setLaji_gps_gap           NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_zzone_val       READ laji_zzone_val         WRITE setLaji_zzone_val         NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_zzone_tmo       READ laji_zzone_tmo         WRITE setLaji_zzone_tmo         NOTIFY paralajiChanged)
    Q_PROPERTY(int laji_curve           READ laji_curve             WRITE setLaji_curve             NOTIFY paralajiChanged)

    Q_PROPERTY(int cal_pt_idx           READ cal_pt_idx             WRITE setCal_pt_idx             NOTIFY paracalChanged)
    Q_PROPERTY(int cal_fd_idx           READ cal_fd_idx             WRITE setCal_fd_idx             NOTIFY paracalChanged)
    Q_PROPERTY(int cal_cp               READ cal_cp                 WRITE setCal_cp                 NOTIFY paracalChanged)
    Q_PROPERTY(int cal_pwd              READ cal_pwd                NOTIFY pwdChanged)
    Q_PROPERTY(int cal_t                READ cal_t                  WRITE setCal_t                  NOTIFY paracalChanged)
    Q_PROPERTY(int cal_load1            READ cal_load1              WRITE setCal_load1              NOTIFY paracalChanged)
    Q_PROPERTY(int cal_load2            READ cal_load2              WRITE setCal_load2              NOTIFY paracalChanged)
    Q_PROPERTY(int cal_load3            READ cal_load3              WRITE setCal_load3              NOTIFY paracalChanged)
    Q_PROPERTY(int cal_load4            READ cal_load4              WRITE setCal_load4              NOTIFY paracalChanged)
    //这里声明的bin_name，在qml中使用，和C++不必相同。
    Q_PROPERTY(QString bin_name         READ bin_name               WRITE setBin_name               NOTIFY bin_nameChanged)

    //提示信息相关：颜色，内容，是否显示。
    //这么写不行 Q_PROPERTY(QString txt_color        READ txt_color) //              WRITE settxt_color              CONSTANT)              //NOTIFY txt_ok_vChanged)     //信息的字颜色，一般是绿或红。
    Q_PROPERTY(QString txt_color        READ txt_color              NOTIFY txt_ok_vChanged)         //NOTIFY txt_ok_vChanged)     //信息的字颜色，一般是绿或红。
    Q_PROPERTY(QString txt_info         READ txt_info               NOTIFY txt_ok_vChanged)         //                CONSTANT)               //NOTIFY txt_ok_vChanged)     //信息的内容，一般是“成功”或“失败”。
    Q_PROPERTY(bool txt_ok_v            READ txt_ok_v               WRITE setTxt_ok_v               NOTIFY txt_ok_vChanged)     //Boot.qml中升级成功提示信息是否显示。

    Q_PROPERTY(bool v_txt_info_cal      READ v_txt_info_cal         WRITE setV_txt_info_cal         NOTIFY txt_ok_vChanged)     //Cal.qml中升级成功提示信息是否显示。
    Q_PROPERTY(bool v_txt_info_key      READ v_txt_info_key         WRITE setV_txt_info_key         NOTIFY txt_ok_vChanged)     //Key.qml中升级成功提示信息是否显示。
    Q_PROPERTY(bool v_txt_info_set      READ v_txt_info_set         WRITE setV_txt_info_set         NOTIFY txt_ok_vChanged)     //ParaSet.qml中升级成功提示信息是否显示。
    Q_PROPERTY(bool v_txt_info_plc      READ v_txt_info_plc         WRITE setV_txt_info_plc         NOTIFY txt_ok_vChanged)     //ParaPlc.qml中升级成功提示信息是否显示。
    Q_PROPERTY(bool v_txt_info_laji     READ v_txt_info_laji        WRITE setV_txt_info_laji        NOTIFY txt_ok_vChanged)     //ParaLaji.qml中升级成功提示信息是否显示。
    Q_PROPERTY(bool pwd_ok              READ pwd_ok                 WRITE setPwd_ok                 NOTIFY pwd_okChanged)
    Q_PROPERTY(bool btn_update          READ btn_update             WRITE setBtn_update             NOTIFY btn_updateChanged)   //BOOT项的“刷机”按钮的使能/禁止。
    Q_PROPERTY(bool stat_btn_getfile    READ stat_btn_getfile       WRITE setStat_btn_getfile       NOTIFY stat_btn_getfileChanged)   //BOOT项的“获取文件”按钮的使能/禁止。
    Q_PROPERTY(bool stat_tim            READ stat_tim               WRITE setStat_tim               NOTIFY stat_timChanged)     //BOOT项的进度条受Timer的调整。


    QML_NAMED_ELEMENT(AddressType)                                          //QQ
    QML_UNCREATABLE("Enum is not a type")

public:
    enum class AddressType {
        PublicAddress,
        RandomAddress
    };
    Q_ENUM(AddressType)         //这就是注册到QML中。

    DeviceHandler(QObject *parent = nullptr);

    void setDevice(DeviceInfo *device);
    void setAddressType(AddressType type);

    AddressType addressType() const;

    bool measuring() const;
    bool alive() const;

    // Statistics
    int hr() const;
    int time() const;
    float average() const;
    int maxHR() const;
    int minHR() const;
    float calories() const;
//    QByteArray wrValue() const;

    QString bin_name() const;
    void setBin_name(QString name);

    QString txt_color() const;
    void setTxt_color(QString name);

    QString txt_info() const;
    void setTxt_info(QString name);

    float ratio() const;
    void setRatio(float val);

    bool update() const;
    Q_INVOKABLE void setUpdate(bool val);

    int plc_addr() const;
    void setPlc_addr(int val);

    int plc_baud_idx() const;
    void setPlc_baud_idx(int val);

    int plc_fmt_idx() const;
    void setPlc_fmt_idx(int val);

    int plc_interval_idx() const;
    void setPlc_interval_idx(int val);

    int plc_protocol_idx() const;
    void setPlc_protocol_idx(int val);

    int laji_gap_min_idx() const;
    void setLaji_gap_min_idx(int val);

    int laji_gap_max_idx() const;
    void setLaji_gap_max_idx(int val);

    int laji_hold_tmo_idx() const;
    void setLaji_hold_tmo_idx(int val);

    int laji_gps_gap() const;
    void setLaji_gps_gap(int val);

    int laji_zzone_val() const;
    void setLaji_zzone_val(int val);

    int laji_zzone_tmo() const;
    void setLaji_zzone_tmo(int val);

    int laji_curve() const;
    void setLaji_curve(int val);

    int zero_auto_sw_idx() const;
    void setZero_auto_sw_idx(int val);

    int range_zero_trace_idx() const;
    void setRange_zero_trace_idx(int val);

    int range_stab_idx() const;
    void setRange_stab_idx(int val);

    int range_zero() const;
    void setRange_zero(int val);

    int filter_dig_idx() const;
    void setFilter_dig_idx(int val);

    int filter_stab_idx() const;
    void setFilter_stab_idx(int val);

    int sps_idx() const;
    void setSps_idx(int val);

    int in1_define_idx() const;
    void setIn1_define_idx(int val);

    int out1_define_idx() const;
    void setOut1_define_idx(int val);

    int out2_define_idx() const;
    void setOut2_define_idx(int val);

    int cal_pt_idx() const;
    void setCal_pt_idx(int val);

    int cal_fd_idx() const;
    void setCal_fd_idx(int val);

    int cal_cp() const;
    void setCal_cp(int val);

    int cal_pwd() const;
    void setCal_pwd(int val);

    int cal_t() const;
    void setCal_t(int val);

    int cal_load1() const;
    void setCal_load1(int val);

    int cal_load2() const;
    void setCal_load2(int val);

    int cal_load3() const;
    void setCal_load3(int val);

    int cal_load4() const;
    void setCal_load4(int val);

    bool txt_ok_v() const;
    void setTxt_ok_v(bool val);

    bool v_txt_info_cal() const;
    void setV_txt_info_cal(bool val);

    bool v_txt_info_key() const;
    void setV_txt_info_key(bool val);

    bool v_txt_info_set() const;
    void setV_txt_info_set(bool val);

    bool v_txt_info_plc() const;
    void setV_txt_info_plc(bool val);

    bool v_txt_info_laji() const;
    void setV_txt_info_laji(bool val);

    bool pwd_ok() const;
    void setPwd_ok(bool val);

    bool btn_update() const;
    void setBtn_update(bool val);

    bool stat_btn_getfile() const;
    void setStat_btn_getfile(bool val);

    bool stat_tim() const;
    void setStat_tim(bool val);

    Q_INVOKABLE void updateParaUI(void);


signals:
    void measuringChanged();
    void aliveChanged();
    void statsChanged();

    void ratioChanged();            //当m_ratio变化时，发射。
    void update_okChanged();        //当传输所有后，发射。

    void parasetChanged();          //ParaSet.qml下关联的属性。
    void paraplcChanged();          //ParaPlc.qml下关联的属性。
    void paralajiChanged();         //ParaLaji.qml下关联的属性。
    void paracalChanged();          //Cal.qml下关联的属性。
    void pwdChanged();              //Cal.qml下标定密码更新信号，不能和其他参数更新混合。
    void bin_nameChanged();
    void txt_ok_vChanged();         //和仪表数据交互，是否收到应答。收到应答，发射信号，以让QML显示“成功”。
    void pwd_okChanged();
    void btn_updateChanged();
    void stat_btn_getfileChanged();
    void stat_timChanged();


public slots:
    void boot_findbin(uint16_t ip1,uint16_t ip2,uint16_t ip3,uint16_t ip4, uint16_t port);
    void boot_start();
    void startMeasurement();        //C++中声明为pulic slots（或Q_INVOKABLE)，才能在QML可见。
    void stopMeasurement();
    void disconnectService();
    void key_to_periph(uint16_t val);
    void data10h_to_periph(uint16_t addr, uint32_t val);   //
    void data10h_to_periph2(uint16_t addr, uint16_t ip1,uint16_t ip2,uint16_t ip3,uint16_t ip4, uint16_t port);
    void data03h_to_periph(uint16_t addr, uint32_t cnt);   //

    void client_err(QString err);           //关联到ClientThread信号。
    void client_trans_ok(QString name);     //关联到ClientThread信号。

    void connectY();
private:
    //QLowEnergyController
    void serviceDiscovered(const QBluetoothUuid &);     //没有定义为slots，也可以被connect。
    void serviceScanDone();                             //没有定义为slots，也可以被connect。
    void connectionPara(int mtu);                       //没有定义为slots，也可以被connect。为什么？

    //QLowEnergyService
    void serviceStateChanged(QLowEnergyService::ServiceState s);
    void updateHeartRateValue(const QLowEnergyCharacteristic &c,
                              const QByteArray &value);
    void confirmedDescriptorWrite(const QLowEnergyDescriptor &d,
                              const QByteArray &value);
    //
    void writeok(const QLowEnergyCharacteristic &characteristic,
                                const QByteArray &newValue);

    void updateDemoHR();
    void timer_recv_handler();

private:
    void addMeasurement(int value);

    QLowEnergyController *m_control = nullptr;
    QLowEnergyService *m_service = nullptr;         //一个服务，下面有几个特性。可以用服务句柄，去访问特性。
    QLowEnergyDescriptor m_notificationDesc;
    DeviceInfo *m_currentDevice = nullptr;          //private，外部不能直接访问。

    bool m_foundHeartRateService = false;
    bool m_measuring = false;               //初始为false，开启测量时为true，停止测量时为false，且通过Q_PROPERTY(NOTIFY) exposes to QML.
    int m_currentValue = 0, m_min = 0, m_max = 0, m_sum = 0;    //m_sum未Q_PROPERTY，它只用于计算m_avg。
    float m_avg = 0, m_calories = 0;

    // Statistics
    QDateTime m_start;
    QDateTime m_stop;

    QList<int> m_measurements;
    QLowEnergyController::RemoteAddressType m_addressType = QLowEnergyController::PublicAddress;

    QTimer m_demoTimer;

    qint32 cnt_in_curve;    //STA_CURVE下收到的字节总数。
    qint32 sta_ymodem;

    QTimer timer_recv;      //接收超时定时器。


    QLowEnergyCharacteristic wrChar;        //“透传串口服务”下的“写串口特性”。也有“读串口特性”，是通过notification实现。

    QByteArray wrValue;
    quint16 idx_pak;
    quint16 num_pak;            //总包数
    float m_ratio;              //进度条的value，范围是[0.0, 1.0]。
    bool m_update;              //遍历一个设备是否完成。

    //界面控件的属性值对应变量
    int m_plc_addr;
    int m_plc_baud_idx;
    int m_plc_fmt_idx;
    int m_plc_interval_idx;
    int m_plc_protocol_idx;

    int m_zero_auto_sw_idx;
    int m_range_zero_trace_idx;
    int m_range_stab_idx;
    int m_range_zero;
    int m_filter_dig_idx;
    int m_filter_stab_idx;
    int m_sps_idx;
    int m_in1_define_idx;
    int m_out1_define_idx;
    int m_out2_define_idx;

    int m_laji_gap_min_idx;
    int m_laji_gap_max_idx;
    int m_laji_hold_tmo_idx;
    int m_laji_gps_gap;
    int m_laji_zzone_val;
    int m_laji_zzone_tmo;
    int m_laji_curve;

    int m_cal_pt_idx;
    int m_cal_fd_idx;
    int m_cal_cp;
    int m_cal_pwd;
    int m_cal_t;
    int m_cal_load1;
    int m_cal_load2;
    int m_cal_load3;
    int m_cal_load4;

    bool m_txt_ok_v;
    bool m_v_txt_info_cal;      //Cal.qml下的传输提示信息是否显示。
    bool m_v_txt_info_key;
    bool m_v_txt_info_set;
    bool m_v_txt_info_plc;
    bool m_v_txt_info_laji;
    bool m_pwd_ok;
    bool m_btn_update;
    bool m_stat_btn_getfile;
    bool m_stat_tim;

    /*modbus请求时地址和数据。
     * 在发送请求时，填充这些变量。
    */
    quint16 m_addr;             //访问的寄存器地址。
    quint32 m_val32;

    QString m_binfilename;      //仅文件名（显示用，不含路径）。
    QString m_txt_color;
    QString m_txt_info;

    QFile bin_file;
    QTemporaryFile bin_tmp_file;    //当用Open()方法打开设备时创建临时文件,并保证临时文件名是唯一的,不会和本机上的文件同名。
    QByteArray bin_ba;

    ClientThread* client;
};

#endif // DEVICEHANDLER_H
